#ifndef __DECODE_H__
#define __DECODE_H__

#include <arpa/inet.h>
#include <string>

#include "packet.hpp"
#include "pkt-header.h"
#include "libutil.hpp"


enum BoxType
  {
    VT_NULL = 0,
    VT_DATA,
    VT_PTR,    
  } ;

enum DecType
  {
    //**** Decoder codes ****
    DEC_ETHER = 0,
    DEC_IPV4,
    DEC_IPV6,
    DEC_TCP,
    DEC_UDP,
    DEC_ICMP4,
    DEC_ICMP6,

    //**** Control consit numbers ****
    DECODER_COUNT,
    DEC_NULL = -1
  } ;

class Netdec;

class NDecResult;
class NDecFilter;
class NDecMpxFilter;
class NDecMpxRes;
class NDecBox;

class Node;
class SessionRoot;
class Session;
class Record;


//=======================================================
//  Network Object
//=======================================================

typedef u_int32_t boxkey_t;

class NetworkObject 
{
public:
  Timer * timer;

  NetworkObject () : timer (NULL), boxTable (NULL),
		     boxRoot (NULL) {}
  virtual ~NetworkObject () ; 

  inline Table<NDecBox> * setupTable ();
  NDecBox * lookupBox (boxkey_t &key);
  NDecBox * fetchBox (boxkey_t &key, int ttl);
  bool removeBox (boxkey_t &key);
  int getBoxCount ();
  void setTimer (Timer &_timer) { this->timer = &_timer; }

  void feBoxInit ();
  void feBoxNext ();
  bool feBoxEnd ();
  boxkey_t feBoxID ();

private:
  Table<NDecBox> * boxTable;
  NDecBox *boxRoot;
  NDecBox *boxPtr;
};


// ==============================
//  Node Class
// ==============================

class Node : public NetworkObject
{
private:
  static int nodeCount;
  const static int DEBUG_MANAGE = false;
  const static int DEBUG_VAR    = false;
  int lock;
  Table<Node> * nodeTable;
  byte_t * keyData;
  size_t keyLen;
  TimerNode * tnode;

public:  
  Node () : lock (0), nodeTable (NULL), keyData (NULL), keyLen (0), tnode (NULL)
  { nodeCount++; }
  virtual ~Node () { 
    if (this->keyData)
      delete [] this->keyData;

    if (this->tnode)
      this->tnode->disable ();

    nodeCount--; 
  }

  void joinTable (Table<Node> &table, void * key, size_t len);
  void removeSelf ();
  bool isLocked ();
  static int getNodeCount () { return nodeCount; }
  static void nodeTimeout (void * obj);

  inline void retain  () { 
    debug (DEBUG_MANAGE, "retain %d -> %d", this->lock, this->lock + 1);
    assert (this->lock >= 0); 
    this->lock++; 
  }

  inline void release () { 
    debug (DEBUG_MANAGE, "release %d -> %d", this->lock, this->lock - 1);
    assert (this->lock >  0);
    this->lock--; 
  }
};


//=======================================================================
// Session Root Class
//=======================================================================

class SessionRoot
{
public:
  SessionRoot () {};
  virtual ~SessionRoot () {}
};

//=======================================================================
// Session Class
//=======================================================================

class Session : public NetworkObject
{
public:
  // --- Variables -------------------------------
  enum SsnDir
    {
      NUL = 0,
      L2R = 1,
      R2L = 2,  
    } ;

  TimerNode * tnode;

  // --- Functions -------------------------------
  Session () : tnode (NULL), dir (NUL), LNode (NULL), RNode (NULL), 
	       key (NULL), keyLen (0), ssnTable (NULL) 
  { sessionCount++;}
  virtual ~Session () 
  {
    if (this->key)
      free (this->key);
    if (this->tnode)
      this->tnode->disable ();

    if (this->LNode)
      {
	this->LNode->release ();
	if (! this->LNode->isLocked ())
	  this->LNode->removeSelf ();
      }

    if (this->RNode)
      {
	this->RNode->release ();
	if (! this->RNode->isLocked ())
	  this->RNode->removeSelf ();
      }

    sessionCount--;
  }

  void joinTable (Table<Session> &tree, void * key, int len);
  void setKey (void * key, int len);
  void * getKey () { return this->key; }
  int getKeyLen () { return this->keyLen; }
  void setDir (SsnDir d) { this->dir = d; };
  void setNode (Node * l, Node * r);
  Node * getLeftNode () { return this->LNode; }
  Node * getRightNode () { return this->RNode; }
  void setTimeout (Timer &timer, int ttl);
  static void ssnTimeout (void *);

  static int getSessionCount () { return sessionCount; }

private:
  // --- Variables -------------------------------
  SsnDir dir; //  Session direction when starting 
  Node * LNode, * RNode;
  void * key;
  size_t keyLen;
  static int sessionCount;
  Table<Session> * ssnTable;
};




// ==============================
// Netdec Variable
// ==============================

class NDecBox
{
  friend class NetworkObject;

public:
  NDecBox () : key (0), type (VT_NULL), len (0), 
	       tnode (NULL), next (NULL), prev (NULL)
  {
    this->data = NULL;
    this->ptr  = NULL;
  } 
  virtual ~NDecBox () 
  {
    if (this->type != VT_PTR && this->data)
      delete [] data;
    if (this->tnode)
      this->tnode->disable ();
    if (this->isAttached ())
      this->detach ();
  }
  
  void setData (void *p, size_t l, BoxType type);
  void setPtr (void *p) { this->ptr = p; this->type = VT_PTR; }
  void * getPtr () { return this->data; }
  unsigned int getLen () { return this->len; }
  BoxType getType () { return this->type; }
  void setTimeout (Timer &timer, int ttl);

  static void boxTimeout (void * obj);

  bool isAttached () { return (this->next != NULL); }
  void attach (NDecBox * tgt);
  void detach ();

private: 
  boxkey_t key;
  BoxType type;
  union {
    byte_t * data;
    void * ptr;
  } ;
  size_t len;
  TimerNode * tnode;
  NDecBox *next, *prev;
  Table<NDecBox> * boxTable;
};


// ==============================
//  Netdec Filter
// ==============================


class NDecFilter
{
public:

  enum ResultType
    {
      RT_NULL = 0,
      RT_INT,
      RT_RAW,
      RT_BOOL,
      RT_ADDR,
    };

  NDecFilter () : any (true) , resType (RT_NULL),  errmsg (""){} ;
  virtual ~NDecFilter () 
  {
    assert (this->resType != RT_NULL);
  }
  virtual bool setup (std::string &arg) = 0;
  bool exec (NDecResult &res);

  void * getResult (size_t * s = NULL, int idx = 0) ;
  int getResCount () { return this->stack; }


  // coommon function
  ResultType getResultType () { return this->resType; }
  std::string getErrMsg () { return this->errmsg; }

protected:  
  void initResult () { this->stack = 0; }
  void pushResult (void * v, int s);
  void setResultType (ResultType _type) { this->resType = _type; }
  void setErrMsg (std::string _err) { this->errmsg += _err; }
  bool any;

private:
  class FilterResult
  {
  public:    
    void * ptr;
    size_t len;
  } ;

  PTree<FilterResult> resTable;
  int stack;
  ResultType resType;
  std::string errmsg;

  virtual void check (NDecResult &res) = 0;
};


class NDecMpxFilter
{
  // NetworkDecoder Multiplex Filter
private:
  std::string errmsg;
  NDecMpxRes * mpxRes;

protected:
  void setErrMsg (const char * e) { this->errmsg += e; }
  void setResult (NDecMpxRes * _res) { this->mpxRes = _res; }
  
public:
  NDecMpxFilter () : errmsg (""), mpxRes (NULL) {}
  virtual ~NDecMpxFilter () {}  

  virtual bool setup (std::string &arg, void * ptr) = 0;
  virtual bool compile () { return true; }
  virtual int exec (NDecResult &res) = 0;
  const char * getErrMsg () { return this->errmsg.c_str (); }
  void * getResult (int idx);
};

class NDecMpxRes
{
public:
  NDecMpxRes () : array (NULL), count (0) {}
  ~NDecMpxRes () ;
  void addItem (void * p);
  void * lookup (int idx);
  int getCount () { return this->count; }

private:
  void ** array;
  int count;
};

//=======================================================
//  Netdec Result
//=======================================================

class NDecResult
{
private:
  const static int REC_MAX = 255;

  Record * recStack [REC_MAX];
  Record * recArray [DECODER_COUNT];
  int recCount [DECODER_COUNT];
  int sp; // Record stack point 
  bool lock;       // Result data (Packet data) Lock
  Packet pkt;
  Timer * decodeTimer;

public:
  NDecResult () : sp (0), lock (false), decodeTimer (NULL) 
  {
    memset (this->recStack, 0, sizeof (this->recStack)); 
    memset (this->recArray, 0, sizeof (this->recArray));
    memset (this->recCount, 0, sizeof (this->recCount));
  }
  ~NDecResult () {}

  bool setPacket (Packet &p);
  Packet * getPacket ();
  void release ();
  bool push (Record * rec);  

  int getStackCount () { return this->sp; }
  int getPktLen () { return this->pkt.getPktLen (); }
  int getCapLen () { return this->pkt.getCapLen (); }
  Record * getRecord (DecType type) ;
  Record * getRecord (int idx);
  void setDecodeTimer (Timer &timer) { this->decodeTimer = &timer; }
  Timer * getDecodeTimer () { return this->decodeTimer; }

  // Protocol depended functions
  Node * getSrcNode (); // Get IPv4/IPv6 source node
  Node * getDstNode (); // Get IPv4/IPv6 destination node
  Session * getNetworkSession (); // Get IPv4/IPv6 session
  Session * getTransportSession (); // Get TCP/UDP session
  
  struct timeval * getTimeval () { return pkt.getTimeval (); }
};

// ============================= 
// Record Class
// ==============================

class Record
{
  friend class NDecResult;

private:
  int len;
  void * ptr;
  DecType next; //  Next protocol decoder 
  DecType type; //  This record protocol 
  int locking;
  Node * srcNode, * dstNode;
  Session * ssn;
  SessionRoot **rootPtr;

public:
  Record () { this->init (); }
  virtual ~Record () {};

  void init ()
  {
    this->len = 0;
    this->ptr = NULL;
    this->next = DEC_NULL;
    this->type = DEC_NULL;
    this->locking = false;
    this->srcNode = this->dstNode = NULL;
    this->ssn = NULL;
    this->rootPtr = NULL;    
  };

  bool lock ();
  bool unlock ();

  void setSessionRootPtr (SessionRoot ** root) { this->rootPtr = root; }
  SessionRoot ** getSessionRootPtr () { return this->rootPtr ; } 
  bool setSessionRoot (SessionRoot * root);
  SessionRoot * getSessionRoot ();

  void setData (DecType proto, void * ptr, int len);
  void setSession (Session * ssn) { this->ssn = ssn; };
  void setNode (Node * src, Node * dst) 
  { this->srcNode = src; this->dstNode = dst; }
  void setNextDecoder (DecType next) { this->next = next; }

  Node * getSrcNode () { return this->srcNode; }
  Node * getDstNode () { return this->dstNode; }
  Session * getSession () { return this->ssn; }
  void * getPtr () { return this->ptr; }
  DecType getNextDecoder () { return this->next; };

  virtual void release () { this->unlock (); } ;
};

// ==============================
// Decoder Class
// ==============================

class Decoder
{
  friend class Netdec;

private:
  int ssnKeyLen;
  void * ssnKey;
  Timer * timer;

public:
  Decoder () : ssnKeyLen (0), ssnKey (NULL), timer (NULL) {};
  virtual ~Decoder () 
  {
    if (this->ssnKey)
      free (this->ssnKey);
  };

  virtual Record * decode (Packet &pkt, Record &prev) = 0;
  virtual NDecFilter * makeFilter (std::string &name) { return NULL; };
  virtual NDecMpxFilter * makeMpxFilter (std::string &name) { return NULL; }
  void * makeSsnKey (void * k1, void * k2, int len);  

  inline Timer& getTimer () { return *this->timer; }
};



#endif //  __DECODE_H__ 
